#!/usr/bin/env python
# ***** BEGIN LICENSE BLOCK *****
# Version: MPL 1.1/GPL 2.0/LGPL 2.1
# 
# The contents of this file are subject to the Mozilla Public License
# Version 1.1 (the "License"); you may not use this file except in
# compliance with the License. You may obtain a copy of the License at
# http://www.mozilla.org/MPL/
# 
# Software distributed under the License is distributed on an "AS IS"
# basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the
# License for the specific language governing rights and limitations
# under the License.
# 
# The Original Code is Komodo code.
# 
# The Initial Developer of the Original Code is ActiveState Software Inc.
# Portions created by ActiveState Software Inc are Copyright (C) 2000-2011
# ActiveState Software Inc. All Rights Reserved.
# 
# Contributor(s):
#   ActiveState Software Inc
# 
# Alternatively, the contents of this file may be used under the terms of
# either the GNU General Public License Version 2 or later (the "GPL"), or
# the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
# in which case the provisions of the GPL or the LGPL are applicable instead
# of those above. If you wish to allow use of your version of this file only
# under the terms of either the GPL or the LGPL, and not to allow others to
# use your version of this file under the terms of the MPL, indicate your
# decision by deleting the provisions above and replace them with the notice
# and other provisions required by the GPL or the LGPL. If you do not delete
# the provisions above, a recipient may use your version of this file under
# the terms of any one of the MPL, the GPL or the LGPL.
# 
# ***** END LICENSE BLOCK *****

"""Test some Node.js-specific codeintel handling."""

import os
import sys
import re
import operator
from os.path import join, dirname, abspath, exists, basename
from glob import glob
import unittest
import subprocess
import logging

from codeintel2.common import *
from codeintel2.util import indent, dedent, banner, markup_text, unmark_text
from codeintel2.environment import SimplePrefsEnvironment

from testlib import TestError, TestSkipped, TestFailed, tag
from citestsupport import CodeIntelTestCase, run, writefile
from distutils.version import LooseVersion


log = logging.getLogger("test")

def write_files(test_case, manifest={}, name="unnamed", env=None):
    """
    Wrapper to write out the files for testing
    @param hash of file name to text content
        The file "test.js" will be run through unmark_text
    @param name the name of the test
    @param env Optional environment for test.js file.
    @return tuple (buf, positions)
        buf is a buffer of the resulting test.js
        positions is the positions returned from unmark_text
    """
    assert len(manifest) > 0, "No manifest"
    assert "test.js" in manifest, "No test.js to run"
    test_dir = join(test_case.test_dir, "test_nodejs_%s" % name)
    test_js = None
    for name, content in manifest.items():
        content = dedent(content)
        path = join(test_dir, name)
        if name == "test.js":
            content, positions = unmark_text(content)
            test_js = path
        writefile(path, content)
    buf = test_case.mgr.buf_from_path(test_js, lang="Node.js", encoding="utf-8", env=env)
    # Our files may include subdirectories, which won't get scanned by
    # default (because curdirlib doesn't want to be recursive).  Manually
    # ensure everything is scanned here.
    curdirlib = buf.libs[0] # XXX: make this not so fragile
    dirs = set(curdirlib.dirs)
    for name in manifest.keys():
        dirname, basename = os.path.split(name)
        absdir = join(test_dir, dirname).rstrip(os.path.sep)
        if not absdir in dirs:
            curdirlib.ensure_dir_scanned(absdir)
            dirs.add(absdir)
    curdirlib.dirs = tuple(dirs)
    return (buf, positions)

class CodeIntelNodeJSTestCase(CodeIntelTestCase):
    test_dir = join(os.getcwd(), "tmp")
    
    # Many of these tests absoluately require NodeJS < 0.10. Create a fake
    # node executable that outputs such a version number and point to it via
    # the _ci_env_prefs_ dictionary.
    fake_node = join(test_dir, "fake_node")
    if not exists(fake_node):
        f = open(fake_node, 'wb')
        f.write("#!/bin/sh\n\necho v0.8.0")
        f.close()
        os.chmod(fake_node, 0755)
    _ci_env_prefs_ = {
        'nodejsDefaultInterpreter': fake_node
    }


class CplnTestCase(CodeIntelNodeJSTestCase):
    lang = "Node.js"
    
    def test_require(self):
        """
        Check that require() works for relative paths
        """
        manifest = {
            "http.js": """
                /* as generated by node_html_to_js.py */
                var http_ = {};
                http_.Server = function Server() {}
                http_.Server.prototype = {}
                /**
                 * Start a UNIX socket server listening for connections on the given path.
                 */
                http_.Server.prototype.listen = function() {}
                exports = http_;
                """,
            "fs.js": """
                /* possible alternative for manually written files */
                exports = {
                    rename: function() {}
                }
            """,
            "test.js": """
                var my_http = require('./http');
                var my_fs = require('./fs');
                my_http.<1>;
                my_fs.<2>;
                """,
        }
        buf, positions = write_files(self, manifest=manifest, name="require")
        self.assertCompletionsInclude2(buf, positions[1],
            [("class", "Server"), ])
        self.assertCompletionsInclude2(buf, positions[2],
            [("function", "rename"), ])

    def test_require_nonvar(self):
        """
        Test require() without intermediate assignment
        """
        manifest = {
            "test.js": """
                require('./dummy').<1>;
                """,
            "dummy.js": """
                exports = {
                    method: function() {}
                };
                """,
        }
        buf, positions = write_files(self, manifest=manifest, name="require_nonvar")
        self.assertCompletionsInclude2(buf, positions[1],
            [("function", "method"), ])

    def test_require_module_exports(self):
        """
        Test exporting via module.exports
        """
        manifest = {
            "test.js": """
                require('./dummy').<1>;
                """,
            "dummy.js": """
                module.exports = {
                    method: function() {}
                };
                """,
        }
        buf, positions = write_files(self, manifest=manifest, name="require_module_exports")
        self.assertCompletionsInclude2(buf, positions[1],
            [("function", "method"), ])

    def test_require_not_buf_path(self):
        """
        Test require() from a path that is not the buffer path
        """
        manifest = {
            "test.js": """
                require('./subdir/proxy').<1>;
                """,
            "subdir/proxy.js": """
                exports = require('../target');
                """,
            "target.js": """
                exports = {
                    method: function() {}
                };
                """,
        }
        buf, positions = write_files(self, manifest=manifest, name="require_not_buf_path")
        self.assertCompletionsInclude2(buf, positions[1],
            [("function", "method"), ])

    def test_module_simple(self):
        """
        Test require() using node_modules (simple case)
        """
        manifest = {
            "test.js": """
                require('simple').<1>;
                """,
            "node_modules/simple/index.js": """
                exports = {
                    simpleMethod: function() {}
                };
                """,
        }
        buf, positions = write_files(self, manifest=manifest, name="module_simple")
        self.assertCompletionsInclude2(buf, positions[1],
            [("function", "simpleMethod"), ])

    def test_module_package_manifest(self):
        """
        Test require() on a module using package.json
        """
        manifest = {
            "test.js": """
                require('simple').<1>;
                """,
            "node_modules/simple/package.json": """
                {
                    "foopy": "pants",
                    "main": "./lib/file.js",
                    "name": "sillypants"
                }
                """,
            "node_modules/simple/lib/file.js": """
                exports = {
                    method: function() {}
                };
                """,
        }
        buf, positions = write_files(self, manifest=manifest, name="module_package_manifest")
        self.assertCompletionsInclude2(buf, positions[1],
            [("function", "method"), ])

    def test_require_prefer_core(self):
        """
        Check that require() prefers core modules where available
        """
        manifest = {
            "test.js": """
                require('http').<1>;
                """,
            "node_modules/http.js": """
                exports = {}
                """,
        }
        buf, positions = write_files(self, manifest=manifest, name="require_prefer_core")
        self.assertCompletionsInclude2(buf, positions[1],
            [("function", "createServer"), ])

    def test_modules_no_repeat_subdir(self):
        """
        Check that we don't descend into foo/node_modules/node_modules
        """
        manifest = {
            "test.js": """
                require('module').good.<1>;
                require('module').bad.<2>;
                """,
            "node_modules/module.js": """
                exports.good = require('good');
                exports.bad = require('bad');
                """,
            "node_modules/good.js": """
                exports = {
                    "other": function() {}
                }
                """,
            "node_modules/node_modules/bad.js": """
                exports = {
                    method: function() {}
                }
                """,
        }
        buf, positions = write_files(self, manifest=manifest, name="modules_no_repeat_subdir")
        self.assertCompletionsInclude2(buf, positions[1],
            [("function", "other"), ])
        self.assertCompletionsDoNotInclude2(buf, positions[2],
            [("function", "method"), ])

    def test_modules_updir(self):
        """
        Test finding modules up the directory tree
        This also tests that multiple files with the same base name works
        """
        manifest = {
            "test.js": """
                require('entry').<1>;
                """,
            "node_modules/entry/index.js": """
                exports = require('trampoline');
                """,
            "node_modules/entry/node_modules/trampoline/index.js": """
                exports = require('bounce');
                """,
            "node_modules/entry/node_modules/trampoline/node_modules/bounce/index.js": """
                exports = require('target');
                """,
            "node_modules/entry/node_modules/target.js": """
                exports = {
                    method: function() {}
                }
                """,
        }
        buf, positions = write_files(self, manifest=manifest, name="modules_updir")
        self.assertCompletionsInclude2(buf, positions[1],
            [("function", "method"), ])

    @tag("bug90331")
    def test_require_extras(self):
        """
        Check that we can tack extra properties onto require()d objects
        """
        manifest = {
                "test.js": """
                    require('./foo').<1>;
                    """,
                "foo.js": """
                    exports = require('./bar');
                    exports.foo = function() {};
                    """,
                "bar.js": """
                    exports = {
                        bar: function() {}
                    }
                    """,
        }
        buf, positions = write_files(self, manifest=manifest, name="require_extras")
        self.assertCompletionsInclude2(buf, positions[1],
            [("function", "foo"),
             ("function", "bar"),
            ])

    def test_globals(self):
        """
        Test that the documented globals are available
        """
        manifest = {
            "test.js": """
                con<1>;
                pro<2>;
                req<3>;
                __f<4>;
                cle<5>;
                set<6>;
                __d<7>;
                glo<8>;
                Buf<9>;
                mod<10>;
                exp<11>;
                """,
        }
        buf, positions = write_files(self, manifest=manifest, name="globals")
        self.assertCompletionsInclude2(buf, positions[1],
            [("variable", "console"),
            ])
        self.assertCompletionsInclude2(buf, positions[2],
            [("variable", "process"),
            ])
        self.assertCompletionsInclude2(buf, positions[3],
            [("function", "require"),
            ])
        self.assertCompletionsInclude2(buf, positions[4],
            [("variable", "__filename"),
            ])
        self.assertCompletionsInclude2(buf, positions[5],
            [("variable", "clearTimeout"),
             ("variable", "clearInterval"),
            ])
        self.assertCompletionsInclude2(buf, positions[6],
            [("variable", "setTimeout"),
             ("variable", "setInterval"),
            ])
        self.assertCompletionsInclude2(buf, positions[7],
            [("variable", "__dirname"),
            ])
        self.assertCompletionsInclude2(buf, positions[8],
            [("variable", "global"),
            ])
        self.assertCompletionsInclude2(buf, positions[9],
            [("variable", "Buffer"),
            ])
        self.assertCompletionsInclude2(buf, positions[10],
            [("namespace", "module"),
            ])
        self.assertCompletionsInclude2(buf, positions[11],
            [("variable", "exports"),
            ])

    def test_globals_props(self):
        """
        Test that the imported globals have the right properties
        """
        manifest = {
            "test.js": """
                require.<1>;
                process.<2>;
                console.<3>;
                Buffer.<4>;
                module.<5>;
                """,
        }
        buf, positions = write_files(self, manifest=manifest, name="globals")
        self.assertCompletionsInclude2(buf, positions[1],
            [("function", "resolve"),
             ("variable", "cache"),
            ])
        self.assertCompletionsInclude2(buf, positions[2],
            [("variable", "stdout"),
             ("variable", "stderr"),
             ("variable", "stdin"),
             ("function", "exit"),
             # the rest are tested in test_nodejs_process
            ])
        self.assertCompletionsInclude2(buf, positions[3],
            [("function", "log"),
             ("function", "info"),
             ("function", "warn"),
             # the rest are tested in test_nodejs_console
            ])
        self.assertCompletionsInclude2(buf, positions[4],
            [("function", "isBuffer"),
             ("function", "byteLength"),
             # the rest are tested in test_nodejs_buffer
            ])
        self.assertCompletionsInclude2(buf, positions[5],
            [("namespace", "exports"),
            ])

    def test_global_accessor(self):
        """
        Test that the Node.js global accessor, |global|, is usable
        """
        manifest = {
            "test.js": """
                global.foo = new Array();
                foo.<1>;
                """,
        }
        buf, positions = write_files(self, manifest=manifest, name="global_accessor")
        self.assertCompletionsInclude2(buf, positions[1],
            [("function", "concat"),
            ])

    def test_globals_no_pollute(self):
        """
        Test that the modules don't pollute the global namespace
        """
        manifest = {
            "test.js": """
                tim<1>;
                """,
        }
        buf, positions = write_files(self, manifest=manifest, name="globals_no_pollute")
        self.assertCompletionsDoNotInclude2(buf, positions[1],
            [("variable", "timers"),
            ])

    @tag("bug90485")
    def test_callback_types(self):
        """
        Test for completion of callback arguments
        """
        manifest = {
            "test.js": """
                var http = require('http');
                http.createServer(function(req, res) {
                    req.<1>;
                    res.<2>;
                })
                """,
        }
        buf, positions = write_files(self, manifest=manifest, name="callback_types")
        self.assertCompletionsInclude2(buf, positions[1],
            [("function", "pause"),
            ])
        self.assertCompletionsDoNotInclude2(buf, positions[1],
            [("function", "writeHead"),
            ])
        self.assertCompletionsInclude2(buf, positions[2],
            [("function", "writeContinue"),
             ("function", "writeHead"),
            ])
            
    def test_anon_func_call_this_module(self):
        """
        Test for handling anonymous function call wrapper around a module.
        (This construct is used to prevent global namespace pollution.)
        """
        manifest = {
            "test.js": """
                require('./dummy').<1>;
                """,
            "dummy.js": """
                (function() {
                    module.exports = {
                        method: function() {}
                    };
                }).call(this);
                """,
        }
        buf, positions = write_files(self, manifest=manifest, name="anon_func_call_this_module")
        self.assertCompletionsAre2(buf, positions[1],
            [("function", "method"), ])

    def test_namespace_mapping(self):
        """Test namespace mapping support."""
        manifest = {
            "test.js": """
                require('ko/editor').<1>;
                require('ko/menu').<2>;
                require('ko/benchmark').<3>;
                require('ko/dom').<4>;
                """,
            "sdk/editor.js": """
                /**
                 * The editor sdk.
                 *
                 * @module ko/editor
                 */
                var sdkEditor = function(_scintilla, _scimoz) {
                    this.scimoz = function() { }
                    this.scintilla = function() { }
                };
                
                module.exports = new sdkEditor();
                """,
            "sdk/menu.js": """
                /**
                 * The menu SDK allows you to easily register new menu items
                 *
                 * @module ko/menu
                 */
                (function() {
                    this.register = function() {}
                    this.unregister = function() {}
                }).apply(module.exports)
                """,
            "sdk/benchmark.js": """
                exports.startTiming = function() {}
                exports.endTiming = function() {}
            """,
            "sdk/dom.js": """
                (function() {
                    var $ = function(query, parent) {}
                    $.createElement = function() {}
                    $.create = function() {}
                    module.exports = $;
                })();
            """
        }
        ns_mapping = {
            "ko": join(os.getcwd(), "tmp", "test_nodejs_namespace_mapping", "sdk")
        }
        ns_mapping = "::".join(["##".join([k, v]) for k, v in ns_mapping.items()])
        buf, positions = write_files(self, manifest=manifest, name="namespace_mapping",
                                     env=SimplePrefsEnvironment(nodejsNamespaceMapping=ns_mapping))
        self.assertCompletionsInclude2(buf, positions[1],
            [("function", "scimoz"),
             ("function", "scintilla")])
        self.assertCompletionsInclude2(buf, positions[2],
            [("function", "register"),
             ("function", "unregister")])
        self.assertCompletionsInclude2(buf, positions[3],
            [("function", "startTiming"),
             ("function", "endTiming")])
        self.assertCompletionsInclude2(buf, positions[4],
            [("function", "create"),
             ("function", "createElement")])
             
class StdLibTestCase(CodeIntelNodeJSTestCase):
    """ Code Completion test cases for the Node.js standard library"""
    lang = "Node.js"

    @property
    def version(self):
        if not hasattr(self, "_version"):
            langintel = self.mgr.langintel_from_lang(self.lang)
            v = langintel._get_nodejs_version_from_env(self.mgr.env) or "99999"
            setattr(self, "_version", LooseVersion(v))
        return self._version

    def assertCompletionsInclude2(self, buf, pos, completions, implicit=True):
        """
        Override CodeIntelTestCase.assertCompletionsInclude2 to support versions
        This is same as the original, except the completions can have an
        optional third argument, a {str} that is the condition, e.g.
        ">= 0.8" or ">= 0.6 and < 0.7"
        (currently, only comparison operators and "and" are supported)
        """
        cplns = []
        for cpln in completions:
            if len(cpln) > 2:
                condition = cpln[2]
                tokens = condition.split()
                comp = {"<":  operator.lt,
                        "<=": operator.le,
                        ">":  operator.gt,
                        ">=": operator.ge,
                        "==": operator.eq,
                        "!=": operator.ne,
                       }
                i = 0
                match = True
                while i < len(tokens):
                    try:
                        if tokens[i] in comp:
                            if not comp.get(tokens[i])(self.version, tokens[i + 1]):
                                match = False
                                break
                            i += 1 # skip the version
                            continue
                        assert tokens[i] == "and", \
                            "Can't parse condition %s" % (condition,)
                    finally:
                        i += 1
                if not match:
                    continue
            cplns.append((cpln[0], cpln[1]))
        return super(StdLibTestCase, self).assertCompletionsInclude2(
            buf, pos, cplns, implicit)

    def test__version(self):
        """
        This is a debugging test; it's really just used to print out the
        Node.js version in use
        """
        raise TestSkipped("Using node.js version %s" % (self.version,))

    def test_console(self):
        """
        Test the Node.js console module
        """
        manifest = {"test.js": """
            require('console').<1>;
            """}
        buf, positions = write_files(self, manifest=manifest, name="console")
        self.assertCompletionsInclude2(buf, positions[1],
            [("function", "log"),
             ("function", "info"),
             ("function", "warn"),
             ("function", "error"),
             ("function", "dir"),
             ("function", "time"),
             ("function", "timeEnd"),
             ("function", "trace"),
             ("function", "assert"),
            ])

    def test_timers(self):
        """
        Test the Node.js timers module
        """
        manifest = {"test.js": "require('timers').<1>;"}
        buf, positions = write_files(self, manifest=manifest, name="timers")
        self.assertCompletionsInclude2(buf, positions[1],
            [("function", "setTimeout"),
             ("function", "clearTimeout"),
             ("function", "setInterval"),
             ("function", "clearInterval"),
            ])

    def test_process(self):
        """
        Test the Node.js process module
        """
        manifest = {"test.js": """
            process.<1>;
            process.stdin.<2>;
            process.stdout.<3>;
            process.stderr.<4>;
            """}
        buf, positions = write_files(self, manifest=manifest, name="process")
        self.assertCompletionsInclude2(buf, positions[1],
            [("variable", "stdout"),
             ("variable", "stderr"),
             ("variable", "stdin"),
             ("variable", "argv"),
             ("variable", "execPath"),
             ("function", "abort", ">= 0.8"),
             ("function", "chdir"),
             ("function", "cwd"),
             ("variable", "env"),
             ("function", "exit"),
             ("function", "getgid"),
             ("function", "setgid"),
             ("function", "getuid"),
             ("function", "setuid"),
             ("variable", "version"),
             ("variable", "versions"),
             ("variable", "installPrefix", ">= 0.6 and < 0.7"),
             ("variable", "config", ">= 0.8"),
             ("function", "kill"),
             ("variable", "pid"),
             ("variable", "title"),
             ("variable", "arch"),
             ("variable", "platform"),
             ("function", "memoryUsage"),
             ("function", "nextTick"),
             ("function", "umask"),
             ("function", "uptime"),
             ("function", "hrtime", ">= 0.8"),
            ])

        self.assertCompletionsInclude2(buf, positions[2],
            [("variable", "isRaw", ">= 0.8"),        # tty.ReadStream
             ("function", "setRawMode", ">= 0.8"),   # tty.ReadStream
             ("function", "setKeepAlive", ">= 0.8"), # net.Socket
             ("function", "pipe"),                   # stream.ReadStream
             ("function", "on"),                     # EventEmitter
            ])
        self.assertCompletionsInclude2(buf, positions[3],
            [("variable", "columns", ">= 0.8"),      # tty.WriteStream
             ("variable", "rows", ">= 0.8"),         # tty.WriteStream
             ("function", "setKeepAlive", ">= 0.8"), # net.Socket
             ("function", "write"),                  # stream.WriteStream
             ("function", "on"),                     # EventEmitter
            ])
        self.assertCompletionsInclude2(buf, positions[4],
            [("function", "write")])

    def test_util(self):
        """
        Test the Node.js util module
        """
        manifest = {"test.js": "require('util').<1>;"}
        buf, positions = write_files(self, manifest=manifest, name="util")
        self.assertCompletionsInclude2(buf, positions[1],
            [("function", "format"),
             ("function", "debug"),
             ("function", "error", ">= 0.8"),
             ("function", "puts", ">= 0.8"),
             ("function", "print", ">= 0.8"),
             ("function", "log"),
             ("function", "inspect"),
             ("function", "isArray"),
             ("function", "isRegExp"),
             ("function", "isDate"),
             ("function", "isError"),
             ("function", "pump"),
             ("function", "inherits"),
            ])

    def test_events(self):
        """
        Test the Node.js events module
        """
        manifest = {"test.js": """
            var events = require('events');
            events.<1>;
            var emitter = new events.EventEmitter();
            emitter.<2>;
            """}
        buf, positions = write_files(self, manifest=manifest, name="events")
        self.assertCompletionsInclude2(buf, positions[1],
            [("class", "EventEmitter")])
        self.assertCompletionsInclude2(buf, positions[2],
            [("function", "addListener"),
             ("function", "on"),
             ("function", "once"),
             ("function", "removeListener"),
             ("function", "removeAllListeners"),
             ("function", "setMaxListeners"),
             ("function", "listeners"),
             ("function", "emit"),
            ])

    def test_buffer(self):
        """
        Test the Node.js buffer module
        """
        manifest = {"test.js": """
            var buffer = require('buffer');
            buffer.<1>;
            buffer.Buffer.<2>;
            var buf = new buffer.Buffer();
            buf.<3>;
            """}
        buf, positions = write_files(self, manifest=manifest, name="buffer")
        self.assertCompletionsInclude2(buf, positions[1],
            [("class", "Buffer"),
             ("variable", "INSPECT_MAX_BYTES"),
            ])
        self.assertCompletionsInclude2(buf, positions[2],
            [("function", "isBuffer"),
             ("function", "byteLength"),
             ("function", "concat", ">= 0.8"),
            ])
        self.assertCompletionsInclude2(buf, positions[3],
            [("function", "write"),
             ("function", "toString"),
             # can't test array accessor []
             ("variable", "length"),
             ("function", "copy"),
             ("function", "slice"),
             ("function", "readUInt8"),
             ("function", "readUInt16LE"),
             ("function", "readUInt16BE"),
             ("function", "readUInt32LE"),
             ("function", "readUInt32BE"),
             ("function", "readInt8"),
             ("function", "readInt16LE"),
             ("function", "readInt16BE"),
             ("function", "readInt32LE"),
             ("function", "readInt32BE"),
             ("function", "readFloatLE"),
             ("function", "readFloatBE"),
             ("function", "readDoubleLE"),
             ("function", "readDoubleBE"),
             ("function", "writeUInt8"),
             ("function", "writeUInt16LE"),
             ("function", "writeUInt16BE"),
             ("function", "writeUInt32LE"),
             ("function", "writeUInt32BE"),
             ("function", "writeInt8"),
             ("function", "writeInt16LE"),
             ("function", "writeInt16BE"),
             ("function", "writeInt32LE"),
             ("function", "writeInt32BE"),
             ("function", "writeFloatLE"),
             ("function", "writeFloatBE"),
             ("function", "writeDoubleLE"),
             ("function", "writeDoubleBE"),
             ("function", "fill"),
            ])

    def test_stream(self):
        """
        Test the Node.js stream module
        """
        manifest = {"test.js": """
            var stream = require('stream');
            stream.<1>;
            var readStream = new stream.ReadableStream();
            readStream.<2>;
            var writeStream = new stream.WritableStream();
            writeStream.<3>;
            """}
        buf, positions = write_files(self, manifest=manifest, name="buffer")
        self.assertCompletionsInclude2(buf, positions[1],
            [("class", "ReadableStream"),
             ("class", "WritableStream"),
            ])
        self.assertCompletionsInclude2(buf, positions[2],
            [("function", "on"), # from EventEmitter
             ("variable", "readable"),
             ("function", "setEncoding"),
             ("function", "pause"),
             ("function", "resume"),
             ("function", "destroy"),
             ("function", "destroySoon", ">= 0.6 and < 0.7"),
             ("function", "pipe"),
            ])
        self.assertCompletionsInclude2(buf, positions[3],
            [("function", "on"), # from EventEmitter
             ("variable", "writable"),
             ("function", "write"),
             ("function", "end"),
             ("function", "destroy"),
             ("function", "destroySoon"),
            ])

    def test_string_decoder(self):
        """
        Test the Node.js string_decoder module
        """
        if self.version < "0.8":
            raise TestSkipped("Node.js %s is not at least 0.8" % (self.version,))
        manifest = {"test.js": """
            var string_decoder = require('string_decoder');
            string_decoder.<1>;
            new string_decoder.StringDecoder().<2>;
            """}
        buf, positions = write_files(self, manifest=manifest, name="buffer")
        self.assertCompletionsInclude2(buf, positions[1],
            [("class", "StringDecoder"),
            ])
        self.assertCompletionsInclude2(buf, positions[2],
            [("function", "write"),
            ])

    def test_crypto(self):
        """
        Test the Node.js crypto module
        """
        manifest = {"test.js": """
            var crypto = require('crypto');
            crypto.<1>;
            crypto.createHash("md5").<2>;
            crypto.createHmac("md5", null).<3>;
            crypto.createCipher("aes192", null).<4>;
            crypto.createDecipher("aes192", null).<5>;
            crypto.createSign("RSA-SHA256").<6>;
            crypto.createVerify("RSA-SHA256").<7>;
            crypto.createDiffieHellman(0).<8>;
            """}
        buf, positions = write_files(self, manifest=manifest, name="crypto")
        self.assertCompletionsInclude2(buf, positions[1],
            [("function", "createCredentials"),
             ("function", "createHash"),
             ("function", "createHmac"),
             ("function", "createCipher"),
             ("function", "createCipheriv"),
             ("function", "createDecipher"),
             ("function", "createDecipheriv"),
             ("function", "createSign"),
             ("function", "createVerify"),
             ("function", "createDiffieHellman"),
             ("function", "getDiffieHellman", ">= 0.8"),
             ("function", "pbkdf2"),
             ("function", "randomBytes"),
            ])
        self.assertCompletionsInclude2(buf, positions[2],
            [("function", "update"),
             ("function", "digest"),
            ])
        self.assertCompletionsInclude2(buf, positions[3],
            [("function", "update"),
             ("function", "digest"),
            ])
        self.assertCompletionsInclude2(buf, positions[4],
            [("function", "update"),
             ("function", "final"),
             ("function", "setAutoPadding", ">= 0.8"),
            ])
        self.assertCompletionsInclude2(buf, positions[5],
            [("function", "update"),
             ("function", "final"),
             ("function", "setAutoPadding", ">= 0.8"),
            ])
        self.assertCompletionsInclude2(buf, positions[6],
            [("function", "update"),
             ("function", "sign"),
            ])
        self.assertCompletionsInclude2(buf, positions[7],
            [("function", "update"),
             ("function", "verify"),
            ])
        self.assertCompletionsInclude2(buf, positions[8],
            [("function", "generateKeys"),
             ("function", "computeSecret"),
             ("function", "getPrime"),
             ("function", "getGenerator"),
             ("function", "getPublicKey"),
             ("function", "getPrivateKey"),
             ("function", "setPublicKey"),
             ("function", "setPrivateKey"),
            ])

    def test_tls(self):
        """
        Test the Node.js tls module
        """
        manifest = {"test.js": """
            require('tls').<1>;
            require('tls').createServer({}, function(s){}).<2>;
            require('tls').connect(80).<3>;
            require('tls').createSecurePair().<4>;
            """}
        buf, positions = write_files(self, manifest=manifest, name="tls")
        self.assertCompletionsInclude2(buf, positions[1],
            [("function", "connect"),
             ("function", "createServer"),
             ("function", "createSecurePair"),
            ])
        self.assertCompletionsInclude2(buf, positions[2],
            [("function", "listen"),
             ("function", "close"),
             ("function", "address"),
             ("function", "addContext"),
             ("variable", "maxConnections"),
             ("variable", "connections"),
             ("function", "on"), # from EventEmitter
            ])
        self.assertCompletionsInclude2(buf, positions[3],
            [("variable", "authorized"),
             ("variable", "authorizationError"),
             ("function", "getPeerCertificate"),
             ("function", "getCipher", ">= 0.8"),
             ("function", "address"),
             ("variable", "remoteAddress"),
             ("variable", "remotePort"),
             ("function", "on"), # from EventEmitter
             ("function", "resume"), # from ReadableStream
             ("function", "write"), # from WritableStream
            ])
        self.assertCompletionsInclude2(buf, positions[4],
            [("function", "on"), # from EventEmitter
            ])

    def test_fs(self):
        """
        Test the Node.js fs module
        """
        manifest = {"test.js": """
            require('fs').<1>;
            require('fs').statSync("/tmp").<2>;
            require('fs').createReadStream("/tmp/foofoo").<3>;
            require('fs').createWriteStream("/tmp/foofoo").<4>;
            require('fs').watch("/tmp/pants").<5>;
            """}
        buf, positions = write_files(self, manifest=manifest, name="fs")
        self.assertCompletionsInclude2(buf, positions[1],
            [("function", "rename"),
             ("function", "renameSync"),
             ("function", "truncate"),
             ("function", "truncateSync"),
             ("function", "chown"),
             ("function", "chownSync"),
             ("function", "fchown"),
             ("function", "fchownSync"),
             ("function", "lchown"),
             ("function", "lchownSync"),
             ("function", "chmod"),
             ("function", "chmodSync"),
             ("function", "fchmod"),
             ("function", "fchmodSync"),
             ("function", "lchmod"),
             ("function", "lchmodSync"),
             ("function", "stat"),
             ("function", "lstat"),
             ("function", "fstat"),
             ("function", "statSync"),
             ("function", "lstatSync"),
             ("function", "fstatSync"),
             ("function", "link"),
             ("function", "linkSync"),
             ("function", "symlink"),
             ("function", "symlinkSync"),
             ("function", "readlink"),
             ("function", "readlinkSync"),
             ("function", "realpath"),
             ("function", "realpathSync"),
             ("function", "unlink"),
             ("function", "unlinkSync"),
             ("function", "rmdir"),
             ("function", "rmdirSync"),
             ("function", "mkdir"),
             ("function", "mkdirSync"),
             ("function", "readdir"),
             ("function", "readdirSync"),
             ("function", "close"),
             ("function", "closeSync"),
             ("function", "open"),
             ("function", "openSync"),
             ("function", "utimes"),
             ("function", "utimesSync"),
             ("function", "futimes"),
             ("function", "futimesSync"),
             ("function", "fsync"),
             ("function", "fsyncSync"),
             ("function", "write"),
             ("function", "writeSync"),
             ("function", "read"),
             ("function", "readSync"),
             ("function", "readFile"),
             ("function", "readFileSync"),
             ("function", "writeFile"),
             ("function", "writeFileSync"),
             ("function", "appendFile", ">= 0.8"),
             ("function", "appendFileSync", ">= 0.8"),
             ("function", "watchFile"),
             ("function", "unwatchFile"),
             ("function", "watch", ">= 0.8"),
             ("function", "exists", ">= 0.8"),
             ("function", "existsSync", ">= 0.8"),
             ("function", "createReadStream"),
             ("function", "createWriteStream"),
            ])
        self.assertCompletionsInclude2(buf, positions[2],
            [("function", "isFile"),
             ("function", "isDirectory"),
             ("function", "isBlockDevice"),
             ("function", "isCharacterDevice"),
             ("function", "isSymbolicLink"),
             ("function", "isFIFO"),
             ("function", "isSocket"),
            ])
        self.assertCompletionsInclude2(buf, positions[3],
            # this is actually from the 'streams' module, which is untestable
            [("function", "addListener"), # from EventEmitter
             ("function", "on"),          # from EventEmitter
             ("variable", "readable"),
             ("function", "setEncoding"),
             ("function", "pause"),
             ("function", "resume"),
             ("function", "destroy"),
             ("function", "destroySoon", ">= 0.6 and < 0.7"),
             ("function", "pipe"),
            ])
        self.assertCompletionsInclude2(buf, positions[4],
            # this is actually from the 'streams' module, which is untestable
            [("function", "addListener"), # from EventEmitter
             ("function", "on"),          # from EventEmitter
             ("variable", "writable"),
             ("function", "write"),
             ("function", "end"),
             ("function", "destroy"),
             ("function", "destroySoon"),
            ])
        self.assertCompletionsInclude2(buf, positions[5],
            [("function", "close"),
             ("function", "on"), # EventEmitter
            ])

    def test_path(self):
        """
        Test the Node.js path module
        """
        manifest = {"test.js": """
            path = require('path');
            path.<1>;
            """}
        buf, positions = write_files(self, manifest=manifest, name="path")
        self.assertCompletionsInclude2(buf, positions[1],
            [("function", "normalize"),
             ("function", "join"),
             ("function", "resolve"),
             ("function", "relative"),
             ("function", "dirname"),
             ("function", "basename"),
             ("function", "extname"),
             ("function", "exists", ">= 0.6 and < 0.7"),
             ("function", "existsSync", ">= 0.6 and < 0.7"),
             ("variable", "sep", ">= 0.8"),
            ])

    def test_net(self):
        """
        Test the Node.js net module
        """
        manifest = {"test.js": """
            net = require('net');
            net.<1>;
            var server = net.createServer();
            server.<2>;
            net.connect().<3>;
            net.createConnection().<4>;
            """}
        buf, positions = write_files(self, manifest=manifest, name="net")
        self.assertCompletionsInclude2(buf, positions[1],
            [("function", "createServer"),
             ("function", "connect"),
             ("function", "createConnection"),
             ("function", "isIP"),
             ("function", "isIPv4"),
             ("function", "isIPv6"),
            ])
        self.assertCompletionsInclude2(buf, positions[2],
            [("function", "on"), # from EventEmitter
             ("function", "listen"),
             ("function", "close"),
             ("function", "address"),
             ("variable", "maxConnections"),
             ("variable", "connections"),
            ])
        for pos in 3, 4:
            self.assertCompletionsInclude2(buf, positions[pos],
                [("function", "on"), # from EventEmitter
                 ("variable", "readable"), # from ReadableStream
                 ("variable", "writable"), # from WritableStream
                 ("function", "connect"),
                 ("variable", "bufferSize"),
                 ("function", "setEncoding"),
                 ("function", "setSecure", ">= 0.6 and < 0.7"),
                 ("function", "write"),
                 ("function", "end"),
                 ("function", "destroy"),
                 ("function", "pause"),
                 ("function", "resume"),
                 ("function", "setTimeout"),
                 ("function", "setNoDelay"),
                 ("function", "setKeepAlive"),
                 ("function", "address"),
                 ("variable", "remoteAddress"),
                 ("variable", "remotePort"),
                 ("variable", "bytesRead"),
                 ("variable", "bytesWritten"),
                ])

    def test_dgram(self):
        """
        Test the Node.js dgram module
        """
        manifest = {"test.js": """
            dgram = require('dgram');
            dgram.<1>;
            var socket = dgram.createSocket("udp4");
            socket.<2>;
            """}
        buf, positions = write_files(self, manifest=manifest, name="dgram")
        self.assertCompletionsInclude2(buf, positions[1],
            [("function", "createSocket"),
            ])
        self.assertCompletionsInclude2(buf, positions[2],
            [("function", "on"), # EventEmitter
             ("function", "send"),
             ("function", "bind"),
             ("function", "close"),
             ("function", "address"),
             ("function", "setBroadcast"),
             ("function", "setTTL"),
             ("function", "setMulticastTTL"),
             ("function", "setMulticastLoopback"),
             ("function", "addMembership"),
             ("function", "dropMembership"),
            ])

    def test_domain(self):
        """
        Test the Node.js domain module
        """
        if not self.version >= "0.8":
            raise TestSkipped("Node.js version %s not at least 0.8" % (self.version,))
        manifest = {"test.js": """
            domain = require('domain');
            domain.<1>;
            domain.create().<2>;
            """}
        buf, positions = write_files(self, manifest=manifest, name="dgram")
        self.assertCompletionsInclude2(buf, positions[1],
            [("function", "create"),
            ])
        self.assertCompletionsInclude2(buf, positions[2],
            [("function", "run"),
             ("variable", "members"),
             ("function", "add"),
             ("function", "remove"),
             ("function", "bind"),
             ("function", "intercept"),
             ("function", "dispose"),
            ])

    def test_dns(self):
        """
        Test the Node.js dns module
        """
        manifest = {"test.js": """
            dns = require('dns');
            dns.<1>;
            """}
        buf, positions = write_files(self, manifest=manifest, name="dns")
        self.assertCompletionsInclude2(buf, positions[1],
            [("function", "lookup"),
             ("function", "resolve"),
             ("function", "resolve4"),
             ("function", "resolve6"),
             ("function", "resolveMx"),
             ("function", "resolveTxt"),
             ("function", "resolveSrv"),
             ("function", "reverse"),
             ("function", "resolveNs"),
             ("function", "resolveCname"),
            ])

    def test_http(self):
        """
        Test the Node.js http module
        """
        manifest = {"test.js": """
            http = require('http');
            http.<1>;
            http.createServer().<2>;
            var request = new http.ServerRequest(); /* not actually valid */
            request.<3>;
            var response = new http.ServerResponse(); /* not actually valid */
            response.<4>;
            http.globalAgent.<5>;
            http.request().<6>;
            var clientResponse = new http.ClientResponse(); /* not actually valid */
            clientResponse.<7>;
            """}
        buf, positions = write_files(self, manifest=manifest, name="http")
        self.assertCompletionsInclude2(buf, positions[1],
            [("function", "createServer"),
             #("function", "createClient"), deprecated
             ("function", "request"),
             ("function", "get"),
             ("variable", "globalAgent"),
            ])
        self.assertCompletionsInclude2(buf, positions[2],
            [("function", "on"), # inherited from EventEmitter
             ("function", "listen"),
             ("function", "close"),
             ("variable", "maxHeadersCount", ">= 0.8"),
            ])
        self.assertCompletionsInclude2(buf, positions[3],
            [("function", "on"), # inherited from EventEmitter
             ("variable", "method"),
             ("variable", "url"),
             ("variable", "headers"),
             ("variable", "trailers"),
             ("variable", "httpVersion"),
             ("function", "setEncoding"),
             ("function", "pause"),
             ("function", "resume"),
             ("variable", "connection"),
            ])
        self.assertCompletionsInclude2(buf, positions[4],
            [("function", "on"), # inherited from EventEmitter
             ("variable", "writable"), # inherited from WritableStream
             ("function", "writeContinue"),
             ("function", "writeHead"),
             ("variable", "statusCode"),
             ("function", "setHeader"),
             ("variable", "sendDate", ">= 0.8"),
             ("function", "getHeader"),
             ("function", "removeHeader"),
             ("function", "write"),
             ("function", "addTrailers"),
             ("function", "end"),
            ])
        self.assertCompletionsInclude2(buf, positions[5],
            [("variable", "maxSockets"),
             ("variable", "sockets"),
             ("variable", "requests"),
            ])
        self.assertCompletionsInclude2(buf, positions[6],
            [("function", "on"), # inherited from EventEmitter
             ("variable", "writable"), # inherited from WritableStream
             ("function", "write"),
             ("function", "end"),
             ("function", "abort"),
             ("function", "setTimeout"),
             ("function", "setNoDelay"),
             ("function", "setSocketKeepAlive"),
            ])
        self.assertCompletionsInclude2(buf, positions[7],
            [("function", "on"), # inherited from EventEmitter
             ("variable", "readable"), # inherited from ReadableStream
             ("variable", "statusCode"),
             ("variable", "httpVersion"),
             ("variable", "headers"),
             ("variable", "trailers"),
             ("function", "setEncoding"),
             ("function", "pause"),
             ("function", "resume"),
            ])

    def test_https(self):
        """
        Test the Node.js https module
        """
        manifest = {"test.js": """
            require('https').<1>;
            require('https').createServer().<2>;
            require('https').request().<3>;
            require('https').get().<4>;
            require('https').globalAgent.<5>;
            """}
        buf, positions = write_files(self, manifest=manifest, name="https")
        self.assertCompletionsInclude2(buf, positions[1],
            [("function", "createServer"),
             ("function", "request"),
             ("function", "get"),
             ("class", "Agent"),
             ("variable", "globalAgent"),
            ])
        self.assertCompletionsInclude2(buf, positions[2],
            [("function", "on"), # inherited from EventEmitter
             ("function", "listen"), # inherited from tls.Server
            ])
        for pos in 3, 4:
            self.assertCompletionsInclude2(buf, positions[pos],
                [("function", "on"), # inherited from EventEmitter
                 ("function", "write"),
                 ("function", "end"),
                 ("function", "abort"),
                ])
        self.assertCompletionsInclude2(buf, positions[5],
            [("variable", "maxSockets"), # inherited from http.Agent
            ])

    def test_url(self):
        """
        Test the Node.js url module
        """
        manifest = {"test.js": """
            url = require('url');
            url.<1>;
            var result = url.parse("");
            result.<2>;
            """}
        buf, positions = write_files(self, manifest=manifest, name="url")
        self.assertCompletionsInclude2(buf, positions[1],
            [("function", "parse"),
             ("function", "format"),
             ("function", "resolve"),
            ])
        self.assertCompletionsInclude2(buf, positions[2],
            [("variable", "href"),
             ("variable", "protocol"),
             ("variable", "host"),
             ("variable", "auth"),
             ("variable", "hostname"),
             ("variable", "port"),
             ("variable", "pathname"),
             ("variable", "search"),
             ("variable", "path"),
             ("variable", "query"),
             ("variable", "hash"),
            ])

    def test_querystring(self):
        """
        Test the Node.js querystring module
        """
        manifest = {"test.js": """
            querystring = require('querystring');
            querystring.<1>;
            """}
        buf, positions = write_files(self, manifest=manifest, name="querystring")
        self.assertCompletionsInclude2(buf, positions[1],
            [("function", "stringify"),
             ("function", "parse"),
             ("function", "escape"),
             ("function", "unescape"),
            ])

    def test_readline(self):
        """
        Test the Node.js readline module
        """
        manifest = {"test.js": """
            require('readline').<1>;
            require('readline').createInterface().<2>;
            """}
        buf, positions = write_files(self, manifest=manifest, name="querystring")
        self.assertCompletionsInclude2(buf, positions[1],
            [("function", "createInterface"),
            ])
        self.assertCompletionsInclude2(buf, positions[2],
            [("function", "on"), # inherited from events.EventEmitter
             ("function", "setPrompt"),
             ("function", "prompt"),
             ("function", "question"),
             ("function", "close"),
             ("function", "pause"),
             ("function", "resume"),
             ("function", "write"),
            ])

    def test_repl(self):
        """
        Test the Node.js repl module
        """
        manifest = {"test.js": """
            repl = require('repl');
            repl.<1>;
            """}
        buf, positions = write_files(self, manifest=manifest, name="repl")
        self.assertCompletionsInclude2(buf, positions[1],
            [("function", "start"),
            ])

    def test_vm(self):
        """
        Test the Node.js vm module
        """
        manifest = {"test.js": """
            vm = require('vm');
            vm.<1>;
            var script = vm.createScript("");
            script.<2>;
            """}
        buf, positions = write_files(self, manifest=manifest, name="vm")
        self.assertCompletionsInclude2(buf, positions[1],
            [("function", "runInThisContext"),
             ("function", "runInNewContext"),
             ("function", "runInContext"),
             ("function", "createContext"),
             ("function", "createScript"),
            ])
        self.assertCompletionsInclude2(buf, positions[2],
            [("function", "runInThisContext"),
             ("function", "runInNewContext"),
            ])

    def test_child_process(self):
        """
        Test the Node.js child_process module
        """
        manifest = {"test.js": """
            child_process = require('child_process');
            child_process.<1>;
            var child = child_process.spawn();
            child.<2>;
            child.stdin.<3>;
            child.stdout.<4>;
            child.stderr.<5>;
            """}
        buf, positions = write_files(self, manifest=manifest, name="child_process")
        self.assertCompletionsInclude2(buf, positions[1],
            [("function", "spawn"),
             ("function", "exec"),
             ("function", "execFile"),
             ("function", "fork"),
            ])
        self.assertCompletionsInclude2(buf, positions[2],
            [("function", "on"), # EventEmitter
             ("variable", "stdin"),
             ("variable", "stdout"),
             ("variable", "stderr"),
             ("variable", "pid"),
             ("function", "kill"),
             ("function", "send"),
             ("function", "disconnect", ">= 0.8"),
            ])
        self.assertCompletionsInclude2(buf, positions[3],
            [("function", "on"), # from EventEmitter
             ("variable", "writable"), # from stream.WritableStream
            ])
        self.assertCompletionsInclude2(buf, positions[4],
            [("function", "on"), # from EventEmitter
             ("variable", "readable"), # from stream.ReadableStream
            ])
        self.assertCompletionsInclude2(buf, positions[5],
            [("function", "on"), # from EventEmitter
             ("variable", "readable"), # from stream.ReadableStream
            ])

    def test_assert(self):
        """
        Test the Node.js assert module
        """
        manifest = {"test.js": """
            assert = require('assert');
            assert.<1>;
            """}
        buf, positions = write_files(self, manifest=manifest, name="assert")
        self.assertCompletionsInclude2(buf, positions[1],
            [("function", "fail"),
             ("function", "ok"),
             ("function", "equal"),
             ("function", "notEqual"),
             ("function", "deepEqual"),
             ("function", "notDeepEqual"),
             ("function", "strictEqual"),
             ("function", "notStrictEqual"),
             ("function", "throws"),
             ("function", "doesNotThrow"),
             ("function", "ifError"),
            ])

    def test_tty(self):
        """
        Test the Node.js tty module
        """
        manifest = {"test.js": """
            tty = require('tty');
            tty.<1>;
            """}
        buf, positions = write_files(self, manifest=manifest, name="tty")
        self.assertCompletionsInclude2(buf, positions[1],
            [("function", "isatty"),
             ("function", "setRawMode"),
            ])

    def test_zlib(self):
        """
        Test the Node.js zlib module
        """
        manifest = {"test.js": """
            require('zlib').<1>;
            require('zlib').createGzip().<2>;
            require('zlib').createGunzip().<3>;
            require('zlib').createDeflate().<4>;
            require('zlib').createInflate().<5>;
            require('zlib').createDeflateRaw().<6>;
            require('zlib').createInflateRaw().<7>;
            require('zlib').createUnzip().<8>;
            """}
        buf, positions = write_files(self, manifest=manifest, name="tty")
        self.assertCompletionsInclude2(buf, positions[1],
            [("function", "createGzip"),
             ("function", "createGunzip"),
             ("function", "createDeflate"),
             ("function", "createInflate"),
             ("function", "createDeflateRaw"),
             ("function", "createInflateRaw"),
             ("function", "createUnzip"),
             ("function", "deflate"),
             ("function", "deflateRaw"),
             ("function", "gzip"),
             ("function", "gunzip"),
             ("function", "inflate"),
             ("function", "inflateRaw"),
             ("function", "unzip"),
             # constants
             ("variable", "Z_OK", ">= 0.8"),
             ("variable", "Z_STREAM_END", ">= 0.8"),
             ("variable", "Z_NEED_DICT", ">= 0.8"),
             ("variable", "Z_ERRNO", ">= 0.8"),
             ("variable", "Z_STREAM_ERROR", ">= 0.8"),
             ("variable", "Z_DATA_ERROR", ">= 0.8"),
             ("variable", "Z_MEM_ERROR", ">= 0.8"),
             ("variable", "Z_BUF_ERROR", ">= 0.8"),
             ("variable", "Z_VERSION_ERROR", ">= 0.8"),
             ("variable", "Z_NO_COMPRESSION", ">= 0.8"),
             ("variable", "Z_BEST_SPEED", ">= 0.8"),
             ("variable", "Z_BEST_COMPRESSION", ">= 0.8"),
             ("variable", "Z_DEFAULT_COMPRESSION", ">= 0.8"),
             ("variable", "Z_FILTERED", ">= 0.8"),
             ("variable", "Z_HUFFMAN_ONLY", ">= 0.8"),
             ("variable", "Z_RLE", ">= 0.8"),
             ("variable", "Z_FIXED", ">= 0.8"),
             ("variable", "Z_DEFAULT_STRATEGY", ">= 0.8"),
             ("variable", "Z_BINARY", ">= 0.8"),
             ("variable", "Z_TEXT", ">= 0.8"),
             ("variable", "Z_ASCII", ">= 0.8"),
             ("variable", "Z_UNKNOWN", ">= 0.8"),
             ("variable", "Z_DEFLATED", ">= 0.8"),
             ("variable", "Z_NULL", ">= 0.8"),
            ])
        for pos in range(2, 9):
            self.assertCompletionsInclude2(buf, positions[pos],
                [("function", "on"), # inherited from events.EventEmitter
                 ("function", "pause"), # inherited from stream.ReadableStream
                 ("function", "write"), # inherited from stream.WritableStream
                ])

    def test_os(self):
        """
        Test the Node.js os module
        """
        manifest = {"test.js": """
            os = require('os');
            os.<1>;
            tty.open().<2>;
            """}
        buf, positions = write_files(self, manifest=manifest, name="os")
        self.assertCompletionsInclude2(buf, positions[1],
            [("function", "tmpDir", ">= 0.8"),
             ("function", "hostname"),
             ("function", "type"),
             ("function", "platform"),
             ("function", "arch"),
             ("function", "release"),
             ("function", "uptime"),
             ("function", "loadavg"),
             ("function", "totalmem"),
             ("function", "freemem"),
             ("function", "cpus"),
             ("function", "networkInterfaces"),
             ("variable", "EOL", ">= 0.8"),
            ])

    def test_cluster(self):
        """
        Test the Node.js cluster module
        """
        manifest = {"test.js": """
            require('cluster').<1>;
            new require('cluster').Worker().<2>;
            """}
        buf, positions = write_files(self, manifest=manifest, name="tty")
        self.assertCompletionsInclude2(buf, positions[1],
            [("variable", "settings", ">= 0.8"),
             ("variable", "isMaster"),
             ("variable", "isWorker"),
             ("function", "setupMaster", ">= 0.8"),
             ("function", "fork"),
             ("function", "disconnect", ">= 0.8"),
             ("variable", "workers", ">= 0.8"),
             #("function", "on"), # EventEmitter, broken due to bug 78596
            ])
        if self.version >= "0.8":
            self.assertCompletionsInclude2(buf, positions[2],
                [("variable", "id"),
                 ("variable", "process"),
                 ("variable", "suicide"),
                 ("function", "send"),
                 ("function", "destroy"),
                 ("function", "disconnect"),
                 ("function", "on"), # EventEmitter
                ])

class CallTipTestCase(CodeIntelNodeJSTestCase):
    lang = "Node.js"

    @tag("bug90482")
    def test_builtin_funcs(self):
        """Check that built-in functions (i.e. things in node_globals) exist"""
        content, positions = unmark_text(dedent("""\
            parseInt(<1>"99999");
            require(<2>"moo");
        """))
        self.assertCalltipMatches(
            markup_text(content, pos=positions[1]),
            r"parseInt\(\w+\s*,\s*\w+\s*\)\s*->\s*Number\s*$",
            flags=re.MULTILINE)
        self.assertCalltipMatches(
            markup_text(content, pos=positions[2]),
            r"require\(\s*\w+\s*\)\s*$",
            flags=re.MULTILINE)

    @tag("bug90482")
    def test_required_funcs(self):
        """ Check that methods auto-generated from Node documentation exist"""
        content, positions = unmark_text(dedent("""\
            require('net').createServer(<1>);
            require('vm').runInThisContext(<2>"code", "filename");
        """))
        self.assertCalltipMatches(
            markup_text(content, pos=positions[1]),
            r"createServer\(\s*\w+\s*,\s*\w+\s*\)\s*$",
            flags=re.MULTILINE)
        self.assertCalltipMatches(
            markup_text(content, pos=positions[2]),
            r"runInThisContext\(\s*\w+\s*,\s*\w+\s*\)\s*$",
            flags=re.MULTILINE)

#---- mainline

if __name__ == "__main__":
    unittest.main()
